﻿(*  
    F# Dataflow for ViewModels
    (c) Andras Janko, 2013, Available under Microsoft Public License.
*)

namespace FsFlow

open System
open System.Collections
open System.Windows
open System.Windows.Controls
open System.Windows.Controls.Primitives
open System.Collections.Specialized
open System.Windows.Interactivity

type BindableSelectedItemsBehavior() = 
    inherit Behavior<Selector>()
    let [<VolatileField>] mutable updating = false
    let mutable targetSelectedItems: IList option = None
    let mutable sourceSubs = None
    let mutable targetSubs = None

    static let selectedItemsProperty =
        DependencyProperty.Register("SelectedItems", typeof<IChangeList>, typeof<BindableSelectedItemsBehavior>,
            FrameworkPropertyMetadata(null, fun (dpo: DependencyObject) (e: DependencyPropertyChangedEventArgs) ->
                (dpo :?> BindableSelectedItemsBehavior).OnSelectedItemsChanged(e.NewValue) ) )

    static member SelectedItemsProperty = selectedItemsProperty

    member this.SelectedItems
        with get() = this.GetValue(selectedItemsProperty) :?> IChangeList
        and  set (v: IChangeList) = this.SetValue(selectedItemsProperty, box v)
 
    member private this.SetTargetSelectedItems() =
        match targetSelectedItems with 
        | Some si -> for i in this.SelectedItems do si.Add i |> ignore
        | None -> ()
          
    member private this.OnSelectedItemsChanged(newValue) =
        sourceSubs |> Option.iter dispose
        sourceSubs <-
            if newValue <> null then
                this.SetTargetSelectedItems()
                match newValue with
                | :? INotifyCollectionChanged as notify ->
                    let associatedObject = this.AssociatedObject
                    notify.CollectionChanged.Subscribe (fun (e: NotifyCollectionChangedEventArgs) ->
                        if not updating then
                            updating <- true
                            try match targetSelectedItems with
                                | Some si ->
                                    match e.Action with
                                    | NotifyCollectionChangedAction.Add -> for i in e.NewItems do si.Add i |> ignore
                                    | NotifyCollectionChangedAction.Remove -> for i in e.OldItems do si.Remove i |> ignore
                                    | NotifyCollectionChangedAction.Replace ->
                                        for i in e.NewItems do si.Add i |> ignore    
                                        for i in e.OldItems do si.Remove i |> ignore
                                    | NotifyCollectionChangedAction.Reset ->
                                        si.Clear()
                                        for i in this.SelectedItems do si.Add i |> ignore
                                    | _ -> ()
                                | None -> associatedObject.SelectedItem <- if this.SelectedItems.Count = 0 then null else this.SelectedItems.[0]                
                            finally updating <- false ) |> Some
                | _ -> None
            else None       
            
    override this.OnAttached() =
        targetSubs <-
            this.AssociatedObject.SelectionChanged.Subscribe (fun (e: SelectionChangedEventArgs) ->
                if not updating then
                    updating <- true
                    try if this.SelectedItems <> null then
                            this.SelectedItems.Change(e.RemovedItems, e.AddedItems)
                    finally updating <- false ) |> Some
        targetSelectedItems <-
            match this.AssociatedObject with
            | :? ListBox as lb when lb.SelectionMode <> SelectionMode.Single -> Some lb.SelectedItems
            | :? MultiSelector as ms -> Some ms.SelectedItems
            | _ -> None
        if this.SelectedItems <> null then this.SetTargetSelectedItems()

    override this.OnDetaching() = targetSubs |> Option.iter dispose